﻿#region File and License Information
/*
<File>
	<License>
		Copyright © 2009 - 2017, Daniel Vaughan. All rights reserved.
		This file is part of Calcium (http://calciumsdk.net), 
		which is released under the MIT License.
		See file /Documentation/License.txt for details.
	</License>
	<CreationDate>2008-11-03 23:22:06Z</CreationDate>
</File>
*/
#endregion

using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

using Outcoder.Concurrency;

#if NETFX_CORE
using Windows.System.Threading;
#endif

namespace Outcoder.Concurrency
{
	/// <summary>
	/// Used to allow calls to be marshaled to a particular thread.
	/// <seealso cref="SynchronizationContext"/>
	/// </summary>
	public interface ISynchronizationContext
	{
		/// <summary>
		/// Invokes the specified callback asynchronously.
		/// Method returns immediately upon queuing the request.
		/// </summary>
		/// <param name="callback">The delegate to invoke.</param>
		/// <param name="state">The state to pass to the invocation.</param>
		void InvokeWithoutBlocking(SendOrPostCallback callback, object state, 
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0);

		/// <summary>
		/// Invokes the specified callback asynchronously.
		/// Method returns immediately upon queuing the request.
		/// </summary>
		/// <param name="action">The delegate to invoke.</param>
		void InvokeWithoutBlocking(Action action,
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0);

		/// <summary>
		/// Invokes the specified callback synchronously.
		/// Method blocks until the specified callback completes.
		/// </summary>
		/// <param name="callback">The delegate to invoke.</param>
		/// <param name="state">The state to pass to the invocation.</param>
		void InvokeAndBlockUntilCompletion(SendOrPostCallback callback, object state,
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0);

		/// <summary>
		/// Invokes the specified callback synchronously.
		/// Method blocks until the specified callback completes.
		/// </summary>
		/// <param name="action">The delegate to invoke.</param>
		void InvokeAndBlockUntilCompletion(Action action,
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0);

		Task InvokeIfRequiredAsync(Func<Task> action, 
			[CallerMemberName] string memberName = null, 
			[CallerFilePath] string filePath = null, 
			[CallerLineNumber] int lineNumber = 0);

		/// <summary>
		/// If the calling thread is matches the thread used by the synchronization context
		/// then the action is invoked on the calling thread; if not, the action is invoked 
		/// on the synchronization context thread.
		/// </summary>
		/// <param name="action"></param>
		void InvokeIfRequired(Action action,
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0);

		/// <summary>
		/// Gets a value indicating whether invocation is required.
		/// That is, it determines whether the call was made from a thread other 
		/// than the one that the current instance was created on.
		/// </summary>
		/// <value><c>true</c> if the calling thread was not the thread that the current instance 
		/// was initialized on; otherwise, <c>false</c>.</value>
		bool InvokeRequired { get; }

		/// <summary>
		/// Initializes this instance.
		/// </summary>
		void Initialize();

#if !MONODROID && !__IOS__ && !NETFX_CORE
		/// <summary>
		/// Initializes this instance using the specified dispatcher.
		/// </summary>
		/// <param name="dispatcher">The dispatcher used to provide synchronization.</param>
		void Initialize(System.Windows.Threading.Dispatcher dispatcher);
#endif
	}

	public static class SynchronizationContextExtensions
	{
		/// <summary>
		/// Invokes the specified callback asynchronously.
		/// Method returns immediately upon queuing the request.
		/// </summary>
		/// <param name="context"></param>
		/// <param name="deferralDepth">This optional parameter invokes 
		/// the action after deferralDepth passes of the context.
		/// For example, a deferralDepth of 2 sees the action is invoked 
		/// only after two passes by the context.
		/// A value of 0 behaves the same as BeginInvoke and is placed directly 
		/// on the context queue and invoked immediately.</param>
		/// <param name="action">The delegate to invoke.</param>
		/// <param name="memberName"></param>
		/// <param name="filePath"></param>
		/// <param name="lineNumber"></param>
		public static void InvokeWithoutBlocking(this ISynchronizationContext context, uint deferralDepth, Action action, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = 0)
		{
			ArgumentValidator.AssertNotNull(context, "context");
			ArgumentValidator.AssertNotNull(action, "action");

			InvokeWithoutBlockingCore(context, deferralDepth, action);
		}

		static void InvokeWithoutBlockingCore(ISynchronizationContext context, uint depth, Action action, [CallerMemberName] string memberName = null, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = 0)
		{
			if (depth < 1)
			{
				context.InvokeWithoutBlocking(action, memberName, filePath, lineNumber);
				return;
			}

			context.InvokeWithoutBlocking(() => InvokeWithoutBlockingCore(context, depth - 1, action, memberName, filePath, lineNumber));
		}

		/// <summary>
		/// Invokes the specified action after the specified delay in milliseconds.
		/// Note: Be sure to wrap the action code in a try catch, 
		/// as exceptions raised by the action are not handled.
		/// </summary>
		/// <param name="context">The context on which the action will be invoked.</param>
		/// <param name="action">The action to execute once the delay expires.</param>
		/// <param name="delayMs">The time to wait before executing the action.</param>
		public static async Task InvokeWithDelay(this ISynchronizationContext context, Action action, int delayMs)
		{
			ArgumentValidator.AssertNotNull(context, "context");
			ArgumentValidator.AssertNotNull(action, "action");
			ArgumentValidator.AssertGreaterThan(delayMs, -1, "delayMs");

			if (delayMs == 0)
			{
				action();
				return;
			}

#if NETFX_CORE
			await ThreadPool.RunAsync(async delegate
			{
				await Task.Delay(delayMs);
				context.InvokeWithoutBlocking(action);
			});

			//return TaskUtility.CreateTaskWithResult(); /* Should use a TaskCompletionSource but this block will be retired. */
#else
			ThreadPool.QueueUserWorkItem(delegate
			{
				Thread.Sleep(delayMs);
				context.InvokeWithoutBlocking(action);
			});
#endif
		}

		/// <summary>
		/// Invokes the specified action after the specified delay in milliseconds.
		/// Note: Be sure to wrap the action code in a try catch, 
		/// as exceptions raised by the action are not handled.
		/// </summary>
		/// <param name="context">The context on which the action will be invoked.</param>
		/// <param name="action">The action to execute once the delay expires.</param>
		/// <param name="delayMs">The time to wait before executing the action.</param>
		public static Task<object> InvokeWithDelayAsync(this ISynchronizationContext context, Action action, int delayMs)
		{
			ArgumentValidator.AssertNotNull(context, "context");
			ArgumentValidator.AssertNotNull(action, "action");
			ArgumentValidator.AssertGreaterThan(delayMs, -1, "delayMs");

			var source = new TaskCompletionSource<object>();

			if (delayMs == 0)
			{
				try
				{
					action();
				}
				catch (Exception ex)
				{
					source.SetException(ex);
					return source.Task;
				}

				source.SetResult(null);
				return source.Task;
			}

#if NETFX_CORE
#pragma warning disable 4014
			ThreadPool.RunAsync(async delegate
#pragma warning restore 4014
#else
			ThreadPool.QueueUserWorkItem(delegate
#endif
			{
				try
				{
#if NETFX_CORE
					await Task.Delay(delayMs);
#else
					Thread.Sleep(delayMs);
#endif
					context.InvokeWithoutBlocking(delegate
					{
						try
						{
							action();
						}
						catch (Exception ex)
						{
							source.SetException(ex);
							return;
						}

						source.SetResult(new object());
					});
				}
				catch (Exception ex)
				{
					source.SetException(ex);
				}
			});

			return source.Task;
		}
	}
}
